#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

#include "string.h"
#include "profile.h"
#include "gSensor.h"
#include "psm.h"
#include "gpio_init.h"
#include "commonapi.h"
#include "protocal.h"
#include "hal_wifi.h"
#include "hx71x.h"
#ifdef SOFT_AP
#include "server.h"
#include "nbiot.h"
#else
#include "tcp_client.h"
#include "station.h"
#endif
#include "hp303.h"

#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_wifi.h"
#include "driver/uart.h"

#define PM_ADDR (0x6B << 1)

#define STACK_SIZE_PSM 2048

#define AWAKE_TIMEOUT (10 * 60 * 1000)

static const char* TAG = "psm";

static StaticTask_t xTaskBuffer_psm;
static StackType_t xStack_psm[STACK_SIZE_PSM];
static TickType_t sleep_timer = AWAKE_TIMEOUT / portTICK_PERIOD_MS;
static TickType_t g_tmp_timer;
static TickType_t g_led_timer;
static gSensor_data pre_sensor_data = { 0 };
static uint8_t pm_status;
//uint8_t task_status[task_id_nu];
static uint8_t led_status;
static uint8_t goto_sleep_debounce;
static bool psm_wait_for_allow_sleep(void)
{
#ifdef SOFT_AP
    return nbiot_is_shutdown() && hx717_is_suspend() && hp303_is_suspend() && tcp_server_is_suspend();
#else
    return tcpclient_sleep() && hx717_is_all_sleep();
#endif
}

static int8_t pm_read_register(uint8_t reg, uint8_t* data)
{
    uint8_t res = 0;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, PM_ADDR, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, PM_ADDR + 1, true);
    i2c_master_read_byte(cmd, data, I2C_MASTER_NACK);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "read reg ret: %d\n", ret);
        res = -1;
    }
    else
    {
        res = 0;
    }
    i2c_cmd_link_delete(cmd);
    return res;
}

static int8_t pm_write_register(uint8_t reg, uint8_t data)
{
    int8_t res;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, PM_ADDR, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
    if (ret != ESP_OK)
    {
        res = -1;
        ESP_LOGE(TAG, "write reg ret: %d\n", ret);
    }
    else
    {
        res = 0;
    }
    i2c_cmd_link_delete(cmd);
    return res;
}

static void psm_set_wakeup_src(void)
{
    uint8_t tmp = 0;
    ReadRegister(0x31, &tmp);
    WriteRegister(0x32, 0x09);

    gpio_wakeup_enable(GPIO_GS_INT1, GPIO_INTR_HIGH_LEVEL);
    esp_sleep_enable_gpio_wakeup();
}

static void psm_poweron(void)
{
    TickType_t cur_timer = xTaskGetTickCount();
    led_status = 1;
    gpio_set_level(GPIO_LED, 1);
    pm_status = PM_RUN;
    sleep_timer = cur_timer + AWAKE_TIMEOUT / portTICK_PERIOD_MS;
    g_led_timer = cur_timer + 500 / portTICK_PERIOD_MS;
}

static void psm_run(void)
{
    TickType_t cur_timer = xTaskGetTickCount();
    if (g_led_timer < cur_timer)
    {
        if (led_status == 1)
        {
            led_status = 0;
            g_led_timer = cur_timer + 500 * 4 / portTICK_PERIOD_MS;
        }
        else
        {
            led_status = 1;
            g_led_timer = cur_timer + 500 / portTICK_PERIOD_MS;
        }

        gpio_set_level(GPIO_LED, led_status);
    }
    if (gpio_get_level(GPIO_CHG) == 0 && gpio_get_level(GPIO_CH_PG) == 0)
    {
        gpio_set_level(GPIO_CH_EN, 0);
        ESP_LOGI(TAG, "start charging\n");
        protocal_goto_sleep();
        pm_status = PM_CHRAGING;
    }
    else
    {
        if (g_tmp_timer < cur_timer)
        {
            uint8_t tmp = 0;
            gSensor_data sensor_data = { 0 };

            gSensor_Readxyz(&sensor_data);

            g_tmp_timer = cur_timer + 1000 / portTICK_PERIOD_MS;
            if (pre_sensor_data.AcceX - 500 > sensor_data.AcceX || pre_sensor_data.AcceX + 500 < sensor_data.AcceX /*|| pre_sensor_data.AcceY - 500 > sensor_data.AcceY || pre_sensor_data.AcceY + 500 < sensor_data.AcceY*/ || pre_sensor_data.AcceZ - 500 > sensor_data.AcceZ || pre_sensor_data.AcceZ + 500 < sensor_data.AcceZ)
            {
                ESP_LOGI(TAG, "gSensor: %d,%d,%d\n", sensor_data.AcceX, sensor_data.AcceY, sensor_data.AcceZ);
                if (goto_sleep_debounce++ == 4)
                {
                    sleep_timer = cur_timer + AWAKE_TIMEOUT / portTICK_PERIOD_MS;
                    goto_sleep_debounce = 0;
                }
            }
            else
            {
                if (cur_timer >= sleep_timer)
                {
                    // goto_sleep_debounce = 0;
                    if (tcp_all_client_close())
                    {
                        // sleep_timer = cur_timer + 10*60*1000 / portTICK_PERIOD_MS;
                        pm_status = PM_GOTO_SLEEP;
                        protocal_goto_sleep();
                        //g_tmp_timer = cur_timer + 5 * 60 * 1000 / portTICK_PERIOD_MS;
                        ESP_LOGI(TAG, "go sleep\n");
                    }
                }
            }
            memcpy(&pre_sensor_data, &sensor_data, sizeof(gSensor_data));
        }
    }
}

static void psm_charging(void)
{
    TickType_t cur_timer = xTaskGetTickCount();
    if (gpio_get_level(GPIO_CHG))
    {
        gpio_set_level(GPIO_CH_EN, 1);
        gpio_set_level(GPIO_LED, 0);
        ESP_LOGI(TAG, "stop charging\n");
        uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM);
        esp_restart();
    }
    else
    {
        if (tcp_all_client_close() && wifi_get_status() == WIFI_STATUS_START)
        {
            wifi_stop();
        }
        if (!gpio_get_level(GPIO_CH_STA))
        {
            if (g_led_timer < cur_timer)
            {
                if (led_status == 1)
                {
                    g_led_timer = cur_timer + 100 / portTICK_PERIOD_MS;
                    led_status = 0;
                }
                else
                {
                    g_led_timer = cur_timer + 333 / portTICK_PERIOD_MS;
                    led_status = 1;
                }
                gpio_set_level(GPIO_LED, led_status);
            }
        }
        else
        {
            gpio_set_level(GPIO_LED, 1);
        }
    }
}

static void psm_sleep(void)
{
    TickType_t cur_timer = xTaskGetTickCount();

    if (g_led_timer < cur_timer)
    {
        if (led_status == 1)
        {
            led_status = 0;
            g_led_timer = cur_timer + 500 * 4 / portTICK_PERIOD_MS;
        }
        else
        {
            led_status = 1;
            g_led_timer = cur_timer + 500 / portTICK_PERIOD_MS;
        }

        gpio_set_level(GPIO_LED, led_status);
    }

    if (psm_wait_for_allow_sleep())
    {
        ESP_LOGI(TAG, "stop wifi\n");
        gpio_set_level(GPIO_LED, 0);
        esp_wifi_stop();

        ESP_LOGI(TAG, "set wakeup source\n");
        psm_set_wakeup_src();
        ESP_LOGI(TAG, "sleep\n");
        uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM);
        if (esp_light_sleep_start() != ESP_OK)
        {
            ESP_LOGE(TAG, "sleep failed\n");
        }
        ESP_LOGI(TAG, "wakeup\n");
        uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM);
        esp_restart();
    }
}

static void psm_task(void* ppvParameter)
{
    pm_write_register(0x03, 0xcf);
    pm_write_register(0x02, 0x95);
    while (1)
    {
        vTaskDelay(100 / portTICK_PERIOD_MS);
        switch (pm_status)
        {
        case PM_POWERON:
            psm_poweron();
            break;
        case PM_RUN:
            psm_run();
            break;
        case PM_CHRAGING:
            psm_charging();
            break;
        case PM_GOTO_SLEEP:
            psm_sleep();
            break;
        default:
            break;
        }
    }
}

void psm_task_init(void)
{
    TaskHandle_t xHandle = NULL;
    // Create the task without using any dynamic memory allocation.
    xHandle = xTaskCreateStatic(
        psm_task,             // Function that implements the task.
        "psm",                // Text name for the task.
        STACK_SIZE_PSM,       // Stack size in bytes, not words.
        (void*)NULL,         // Parameter passed into the task.
        tskIDLE_PRIORITY + 1, // Priority at which the task is created.
        xStack_psm,           // Array to use as the task's stack.
        &xTaskBuffer_psm);    // Variable to hold the task's data structure.
    if (xHandle == NULL)
    {
        ESP_LOGE(TAG, "Create psm task fail\n");
    }
}

uint8_t psm_get_status(void)
{
    return pm_status;
}
